# frozen_string_literal: true

module Security
  module Ingestion
    module Tasks
      class UpdateVulnerabilityUuids < AbstractTask
        def execute
          return unless update_uuids?

          ApplicationRecord.transaction do
            update_uuids
          end
        rescue StandardError => error
          Gitlab::ErrorTracking.track_exception(error, project_id: pipeline&.project_id)
          # Do not raise the exception. A failure here
          # should not stop the rest of the ingestion process.
        end

        private

        def build_uuid_hash_lookup
          uuid_hash_lookup = {}
          uuids_to_ignore = existing_uuids(finding_maps.map(&:uuid))
          finding_maps.each do |finding_map|
            finding_map
              .identifiers
              .reject { |identifier| IDENTIFIER_TYPES_TO_NOT_TAKEOVER.include?(identifier.external_type.downcase) }
              .each do |identifier|
              # Do not try to take over if the new uuid
              # already exists in the database
              next if uuids_to_ignore.include?(finding_map.uuid)

              recalculate_uuids(finding_map.report_finding, identifier).each do |recalculated|
                uuid_hash_lookup[recalculated] = finding_map.uuid
              end
            end
          end
          uuid_hash_lookup
        end

        def existing_uuids(finding_map_uuids)
          pipeline.project
            .vulnerability_findings
            .by_uuid(finding_map_uuids)
            .pluck_uuids
        end

        def update_uuids?
          scanners.include?("semgrep")
        end

        def update_uuids
          uuid_hash_lookup = build_uuid_hash_lookup
          UpdateVulnerabilityUuids::VulnerabilityFindings.new(uuid_hash_lookup, project).execute
          UpdateVulnerabilityUuids::VulnerabilityFeedback.new(uuid_hash_lookup, project).execute
          UpdateVulnerabilityUuids::VulnerabilityReads.new(uuid_hash_lookup, project).execute
        end

        IDENTIFIER_TYPES_TO_NOT_TAKEOVER = %w(semgrep_id cwe owasp).freeze

        delegate :project, to: :pipeline, private: true

        def scanners
          @scanners ||= finding_maps.map(&:report_finding).map(&:scanner).map(&:external_id).uniq
        end

        def recalculate_uuids(report_finding, identifier)
          location_uuid = ::Security::VulnerabilityUUID.generate(
            report_type: report_finding.report_type,
            primary_identifier_fingerprint: identifier.fingerprint,
            location_fingerprint: report_finding.location.fingerprint,
            project_id: pipeline.project_id
          )

          tracking_signature_uuid = ::Security::VulnerabilityUUID.generate(
            report_type: report_finding.report_type,
            primary_identifier_fingerprint: identifier.fingerprint,
            location_fingerprint: report_finding.location_fingerprint,
            project_id: pipeline.project_id
          )

          [location_uuid, tracking_signature_uuid]
        end
      end
    end
  end
end
